# Copyright (c) 2018 Presto Labs Pte. Ltd.
# Author: leon

import datetime
import time

import dateutil.parser
import pytz

from coin.exchange.base.kr_rest.private_client_base import PrivateClientBase
from coin.exchange.bittrex_v1.kr_rest.native_private_client import BittrexNativePrivateClient
from coin.exchange.bittrex_v1.kr_rest.product import BittrexProduct
from coin.exchange.bittrex_v1.kr_rest.currency import BittrexCurrency

from coin.proto.coin_query_pb2 import AccountBalance, CurrencyBalance
import coin.proto.coin_order_enums_pb2 as coin_order
import coin.proto.coin_query_pb2 as coin_query
from coin.proto.coin_query_pb2 import (
    AccountBalance,
    CurrencyBalance,
    ProductFillElement,
    ProductFillBundle,
    ProductOrderElement,
    ProductOrderBundle,
)


class BittrexPrivateParser(object):
  @staticmethod
  def parse_balance(update_msg, currency_obj):
    if not update_msg['success']:
      raise ValueError(update_msg)

    account_balance = AccountBalance()
    account_balance.exchange = 'Bittrex'
    result = update_msg['result']
    if currency_obj == 'ALL':
      assert isinstance(result, list)
      for balance_elem in result:
        currency = balance_elem["Currency"]
        currency_balance = CurrencyBalance()
        currency_balance.currency = \
          BittrexCurrency.FromStrNativeCurrency(currency).currency
        currency_balance.currency_native = currency
        currency_balance.available = float(balance_elem['Available'])
        currency_balance.hold = float(balance_elem['Pending'])
        currency_balance.total = balance_elem['Balance']
        assert currency_balance.total == currency_balance.available + currency_balance.hold
        account_balance.each_balance.extend([currency_balance])
    else:
      assert isinstance(result, dict)
      if result['Balance'] is None:
        return account_balance
      currency = result["Currency"]
      if currency != currency_obj.native_currency:
        return account_balance
      currency_balance = CurrencyBalance()
      currency_balance.currency = currency_obj.currency
      currency_balance.currency_native = currency
      currency_balance.available = float(result['Available'])
      currency_balance.hold = float(result['Pending'])
      currency_balance.total = result['Balance']
      assert currency_balance.total == currency_balance.available + currency_balance.hold
      account_balance.each_balance.extend([currency_balance])
    return account_balance

  @staticmethod
  def parse_fill(update_msg, product):
    if not update_msg['success']:
      raise ValueError(update_msg)

    currency_fill_bundle = ProductFillBundle()
    currency_fill_bundle.exchange = 'Bittrex'
    currency_fill_bundle.symbol = product.symbol
    result = update_msg['result']
    for update_elem in result:
      if update_elem["QuantityRemaining"] != 0.0:
        continue
      if update_elem['Exchange'] != product.native_symbol:
        continue
      currency_fill = ProductFillElement()
      currency_fill.symbol = product.symbol
      currency_fill.price = float(update_elem['Limit'])
      currency_fill.qty = float(update_elem['Quantity'])
      currency_fill.fee = float(update_elem['Commission'])
      currency_fill.order_id = update_elem['OrderUuid']
      fill_time = datetime.datetime.strptime(update_elem['TimeStamp'], "%Y-%m-%dT%H:%M:%S.%f")
      fill_time = pytz.utc.localize(fill_time)
      currency_fill.fill_timestamp = int(fill_time.timestamp() * (10**9))
      if update_elem['OrderType'] == 'LIMIT_SELL':
        currency_fill.side = coin_order.SELL_ORDER
      elif update_elem['OrderType'] == 'LIMIT_BUY':
        currency_fill.side = coin_order.BUY_ORDER
      else:
        raise ValueError('Unknown OrderType: ' + update_msg)
      currency_fill_bundle.each_fill.extend([currency_fill])
    return currency_fill_bundle

  @staticmethod
  def parse_orders(update_msg, product):
    if not update_msg['success']:
      raise ValueError(update_msg)

    currency_order_bundle = ProductOrderBundle()
    currency_order_bundle.exchange = 'Bittrex'
    currency_order_bundle.symbol = product.symbol
    for order_elem in update_msg['result']:
      if order_elem['Exchange'] != product.native_symbol:
        continue
      currency_order = ProductOrderElement()
      currency_order.symbol = product.symbol
      currency_order.price = float(order_elem['Price'])
      currency_order.qty = float(order_elem['Quantity'])
      currency_order.order_id = order_elem['OrderUuid']
      open_time = datetime.datetime.strptime(order_elem['Opened'], "%Y-%m-%dT%H:%M:%S.%f")
      open_time = pytz.utc.localize(open_time)
      currency_order.created_timestamp = int(open_time.timestamp() * (10**9))
      if order_elem['OrderType'] == 'LIMIT_SELL':
        currency_order.side = coin_order.SELL_ORDER
      elif order_elem['OrderType'] == 'LIMIT_BUY':
        currency_order.side = coin_order.BUY_ORDER
      else:
        raise ValueError('Unknown OrderType: ' + update_msg)
      currency_order_bundle.each_order.extend([currency_order])
    return currency_order_bundle


class BittrexPrivateClient(PrivateClientBase):
  def __init__(self, key_file):
    PrivateClientBase.__init__(self)
    self.native_client = BittrexNativePrivateClient(key_file=key_file)

  def query_account_balance_impl(self):
    currency = "ALL"
    if currency == 'ALL':
      update = self.native_client.query_account_getbalances()
    else:
      update = self.native_client.query_account_getbalance(currency.native_currency)
    update.msg = BittrexPrivateParser.parse_balance(update.msg, currency)
    return update

  def query_fills_impl(self, product):
    update = self.native_client.query_account_getorderhistory(product.native_symbol)
    update.msg = BittrexPrivateParser.parse_fill(update.msg, product)
    return update

  def query_list_orders_impl(self, product):
    update = self.native_client.query_account_getopenorders(product.native_symbol)
    update.msg = BittrexPrivateParser.parse_orders(update.msg, product)
    return update

  def query_submit_impl(self, product, side, price, qty):
    symbol = product.native_symbol
    if side == 'bid':
      update = self.native_client.query_market_buylimit(symbol, price, qty)
    else:
      update = self.native_client.query_market_selllimit(symbol, price, qty)
    return update

  def query_cancel_impl(self, product, order_id):
    update = self.native_client.query_market_cancel(order_id)
    return update

  def query_cancel_product_impl(self, product):
    orders = self.query_list_orders_impl(product)
    for order in orders.msg.each_order:
      self.query_cancel_impl(product, order.order_id)
      time.sleep(0.5)
